# load district shapefilegerman_districts <- sf::read_sf("./data/VG250_KRS.shp")# load district attributesattributes_districts <- readr::read_csv2("./data/attributes_districts.csv") |> dplyr::mutate(ecar_share =as.numeric(ecar_share))# join datagerman_districts_enhanced <- german_districts |> dplyr::left_join(attributes_districts, by ="AGS")# load states shapefilegerman_states <- sf::read_sf("./data/VG250_LAN.shp")
Adding geoms to a blank canvas
# a simple first map ggplot(data = german_districts_enhanced)
Adding geoms to a blank canvas
# a simple first map ggplot() +geom_sf(data = german_districts_enhanced)
Add the aesthetics
Are you having trouble choosing the right color? Some excellent tutorials exist, f.e, by Michael Toth.
# change color paletteggplot() +geom_sf(data = german_districts_enhanced, aes(fill = ecar_share) ) +# readable with color vision deficienciesscale_fill_viridis_c(option ="plasma", direction =-1)
Add another layer
# the shapefile includes polygons of oceans and lakes# easy fix on the fly when you know your datagerman_states <- german_states |> dplyr::filter(GF ==4)# add layer with German statesggplot() +geom_sf(data = german_districts_enhanced, aes(fill = ecar_share), color =NA ) +scale_fill_viridis_c(option ="plasma", direction =-1 ) +# add another layergeom_sf(data = german_states, # filling transparentfill ="transparent",# color of borderscolor ="black", # size of borderssize =1 )
Save and reuse
Maps produced with ggplot2 are standard objects like any other object in R (they are lists). We can assign them to reuse, plot later, and add map layers.
Furthermore, you can save them just as any ggplot2 graph. The ggsave() function automatically detects the file format. You can also define the height, width, and dpi, which is particularly useful to produce high-class graphics for publications.
Save and reuse
# assign to objectecar_map <-ggplot() +geom_sf(data = german_districts_enhanced, aes(fill = ecar_share), color =NA ) +scale_fill_viridis_c(option ="plasma",direction =-1,name ="E-Car Share",guide =guide_legend(direction="horizontal",label.position ="bottom" ) ) +geom_sf(data = german_states, fill ="transparent", color ="black" ) # save as png-file# ggsave("ecar_map.png", ecar_map, dpi = 300)
Where ggplot2 cannot help anymore
In some specific circumstances, we might realize that ggplot2 is super powerful but not originally designed to build maps. Typical features of maps are not in the package, like a compass or scale bars.
This is where other packages might need to be installed. The good thing: Elements of the package ggspatial can be included as ggplot2 layer. Check out Github.
The extras
ggspatial allows you to add, f.e. a scale bar and a north arrow.
# add scalebar and north arrowecar_map + ggspatial::annotation_scale(location ="br" ) + ggspatial::annotation_north_arrow(location ="tr", style = ggspatial::north_arrow_minimal() )
Making a plan
This map will be our canvas for the ongoing session. There are hundreds of options to change this map. We will cover at least some essential building blocks:
THE MAP: adding attributes, choosing from colors/palettes, adding layers
THE LEGEND: position, sizes, display
THE ENVIRONMENT: choosing from themes and build your own
THE META-INFORMATION: titles and sources
THE EXTRAS: scales and compass
If you are working on your maps, the ggplot2 cheatsheets will help you with an overview of scales, themes, labels, facets, and more.
ggplot2 and raster data
You can perfectly use ggplot2 to create maps with raster data. There are several ways to do so. The easiest way is using the tidyterra package.
Let’s explore the suitability of ggplot2 in combination with tidyterra with a case study on population dynamics in Germany. We are utilizing the population grids from the WorldPop Open Population Repository (WOPR).
# Example with worldpop data for Germany 2020ger_pop_2020 <- terra::rast("../../data/deu_ppp_2020_1km_Aggregated.tif")ger_pop_2020
class : SpatRaster
dimensions : 934, 1100, 1 (nrow, ncol, nlyr)
resolution : 0.008333333, 0.008333333 (x, y)
extent : 5.87375, 15.04042, 47.27458, 55.05792 (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (EPSG:4326)
source : deu_ppp_2020_1km_Aggregated.tif
name : deu_ppp_2020_1km_Aggregated
min value : 0.000
max value : 4513.521
Simple plot with terra/baseR
terra::plot(ger_pop_2020)
Simple plot with terra/baseR
Let’s transform it into ETRS89/UTM 32N (EPSG: 25832). You will detect a slight adjustment to the visualization.
ggplot()+geom_spatraster(data = ger_pop_2020)+scale_fill_viridis_c(n.breaks =12,guide =guide_legend(reverse =TRUE) )+labs(fill ="Population\ncount",title ="Estimated population of Germany in 2020",subtitle ="Approx. 1km grid" )
Exercise 4_1: A simple map
Case study: Population dynamics
We will now dig deeper into population dynamics. We load a second layer which records the population size twenty years earlier - in 2000. We compare dimensions of both layers: they match.
The state (Bundesland) of North Rhine-Westphalia looks quite interesting. The state has both growth and decline regions. We zoom into that region and explore dynamics within and between districts (Kreise).
The state (Bundesland) of North Rhine-Westphalia looks quite interesting. The state has both growth and decline regions. We zoom into that region and explore dynamics within and between districts (Kreise).
# Subset pop data to spatial extent of NRWNRW_pop_diff <- terra::crop( ger_pop_diff, NRW_districts) |> terra::mask(NRW_districts)# Let's visualizeggplot()+geom_spatraster(data = NRW_pop_diff)+geom_sf(data = NRW_districts,fill ="transparent",color ="white",size =2)
Case study: Population dynamics
We are now working with diverging values +/-0. The color palette should reflect that. We have several options to adjust it.
We are now working with diverging values +/-0. The color palette should reflect that. We have several options to adjust it.
# Identify max and min to adjust for skewness of positive and negative valuesmax_val <-max(abs(minmax(NRW_pop_diff)))ggplot()+geom_spatraster(data = NRW_pop_diff)+geom_sf(data = NRW_districts,fill ="transparent",color ="black",size =3)+scale_fill_distiller(type ="div", palette ="PuOr", limits =c(-max_val, max_val) )
Case study: Population dynamics
We are now working with diverging values +/-0. The color palette should reflect that. We have several options to adjust it.
# Or define manually for even greater controlggplot()+geom_spatraster(data = NRW_pop_diff)+geom_sf(data = NRW_districts,fill ="transparent",color ="black",size =3)+scale_fill_gradient2(low ="#2d004b",mid ="white",high ="#b35806",midpoint =0 )
Case study: Population dynamics
We also want to add text labels for the two districts with the highest population growth and decline.
Let’s do some final polishing: 1. Turn NAs transparent, 2. add arrow and scale bar, 3. make final adjustments to the theme, and 4. adjust title, subtitle and caption.